package com.ifacebox.face.track.ui;

import com.arcsoft.face.enums.DetectMode;
import com.github.sarxos.webcam.Webcam;
import com.ifacebox.face.track.cache.FaceUserCache;
import com.ifacebox.face.track.model.FaceConfig;
import com.ifacebox.face.track.model.FaceTrack;
import com.ifacebox.face.track.model.FaceTrackUser;
import com.ifacebox.face.track.model.FaceUser;
import com.ifacebox.face.track.sevice.FaceConfigService;
import com.ifacebox.face.track.sevice.FaceEnginePooledService;
import com.ifacebox.face.track.sevice.FaceEngineService;
import com.ifacebox.face.track.sevice.FaceThreadService;
import com.ifacebox.face.track.utils.DebugUtils;
import com.ifacebox.face.track.utils.ImageUtils;

import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Objects;
import java.util.List;

/**
 * @author znn
 */
public class FaceTrackFrame extends JFrame implements FaceLoadingDialog.Callback, FaceWebCamPanel.Callback {
    private static final ImageIcon LOGO = new ImageIcon(Objects.requireNonNull(FaceTrackFrame.class.getResource("/logo.png")));
    private static final int FACE_TRACK_SIZE = 30, FACE_TRACK_USER_SIZE = 10;
    private FaceEnginePooledService.FaceEnginePool faceTrackPool, faceDetectPool;
    private FaceThreadService faceTrackThreadService, faceDetectThreadService;
    private DefaultListModel<FaceTrack> faceTrackModel;
    private DefaultListModel<FaceTrackUser> faceTrackUserModel;
    private final FaceLoadingDialog faceLoadingDialog;
    private FaceWebCamPanel faceWebCamPanel;
    private FaceConfig faceConfig;

    public FaceTrackFrame() {
        super.setTitle("火眼金睛");
        super.setLayout(new BorderLayout());
        super.setIconImage(LOGO.getImage());
        super.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        super.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                shutdown();
            }
        });
        this.faceLoadingDialog = new FaceLoadingDialog();
        this.faceLoadingDialog.show("正在初始化，请稍后...", this);
    }

    public void shutdown() {
        if (this.faceWebCamPanel != null) {
            this.faceWebCamPanel.stop();
        }
        if (this.faceTrackThreadService != null) {
            this.faceTrackThreadService.shutdown();
        }
        if (this.faceDetectThreadService != null) {
            this.faceDetectThreadService.shutdown();
        }
        if (this.faceTrackPool != null) {
            this.faceTrackPool.close();
        }
        if (this.faceDetectPool != null) {
            this.faceDetectPool.close();
        }
        System.exit(0);
    }

    @Override
    public void call() throws Exception {
        FaceConfigService faceConfigService = new FaceConfigService();
        this.faceConfig = faceConfigService.readConfig();
        //初始化人脸库
        FaceUserCache.getInstance().loadCache((load, size) -> {
            if (load) {
                this.faceLoadingDialog.setValue(0, size);
            }
            this.faceLoadingDialog.call("正在初始化人脸库，请稍后...");
        });
        FaceEnginePooledService faceTrackPooledService = new FaceEnginePooledService(1, DetectMode.ASF_DETECT_MODE_VIDEO);
        this.faceTrackThreadService = new FaceThreadService(faceTrackPooledService.getFaceMaxThreads());
        this.faceTrackPool = faceTrackPooledService.getFaceEnginePool();
        this.faceLoadingDialog.setValue(0, faceTrackPooledService.getFaceMaxThreads());
        this.faceLoadingDialog.call("正在初始化算法，请稍后...", () -> {
            //加载人脸跟踪算法引擎
            this.faceTrackPool.addObject();
        });
        FaceEnginePooledService faceDetectPooledService = new FaceEnginePooledService(DetectMode.ASF_DETECT_MODE_IMAGE);
        this.faceDetectThreadService = new FaceThreadService(faceDetectPooledService.getFaceMaxThreads());
        this.faceDetectPool = faceDetectPooledService.getFaceEnginePool();
        this.faceLoadingDialog.setValue(0, faceDetectPooledService.getFaceMaxThreads());
        for (int i = 0; i < faceDetectPooledService.getFaceMaxThreads(); i++) {
            this.faceLoadingDialog.call("正在初始化算法，请稍后...", () -> {
                //加载人脸检测算法引擎
                this.faceDetectPool.addObject();
            });
        }
        List<Webcam> webCams = Webcam.getWebcams();
        if (webCams == null || webCams.isEmpty()) {
            throw new Exception("没有找到相机！");
        }
        SwingUtilities.invokeLater(() -> this.show(webCams));
    }

    @Override
    public void error(Exception err) {
        JOptionPane.showMessageDialog(this.faceLoadingDialog, err.toString(), err.getMessage(), JOptionPane.ERROR_MESSAGE);
        SwingUtilities.invokeLater(() -> this.show(Webcam.getWebcams()));
        //this.shutdown();
    }

    @Override
    public void call(FaceTrack faceTrack) {
        this.addFaceTrack(faceTrack);
    }

    public void show(List<Webcam> webCams) {
        this.faceWebCamPanel = new FaceWebCamPanel(this.faceTrackThreadService, this.faceTrackPool, this);
        super.add(new JSplitPane(JSplitPane.VERTICAL_SPLIT) {{
            this.setResizeWeight(1.0);
            this.setContinuousLayout(true);
            this.setOneTouchExpandable(true);
            this.setTopComponent(new JSplitPane(JSplitPane.HORIZONTAL_SPLIT) {{
                this.setResizeWeight(1.0);
                this.setContinuousLayout(true);
                this.setOneTouchExpandable(true);
                this.setLeftComponent(faceWebCamPanel);
                this.setRightComponent(new JScrollPane(new JList<FaceTrackUser>() {{
                    this.setVisibleRowCount(1);
                    this.setLayoutOrientation(JList.VERTICAL);
                    this.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
                    this.setModel(faceTrackUserModel = new DefaultListModel<>());
                    this.setCellRenderer(new FaceTrackUserCellRenderer());
                }}) {{
                    this.setMinimumSize(new Dimension(-1, 275));
                }});
            }});
            this.setBottomComponent(new JScrollPane(new JList<FaceTrack>() {{
                this.setVisibleRowCount(1);
                this.setLayoutOrientation(JList.HORIZONTAL_WRAP);
                this.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
                this.setModel(faceTrackModel = new DefaultListModel<>());
                this.setCellRenderer(new FaceTrackCellRenderer());
            }}) {{
                this.setMinimumSize(new Dimension(-1, 110));
            }});
        }}, BorderLayout.CENTER);
        super.add(new FaceTrackToolBar(this, webCams, this.faceDetectPool, this.faceWebCamPanel), BorderLayout.NORTH);
        super.add(new FaceStatusToolBar(), BorderLayout.SOUTH);
        super.pack();
        super.setLocationRelativeTo(null);
        super.setVisible(true);
    }

    public void addFaceTrack(FaceTrack faceTrack) {
        SwingUtilities.invokeLater(() -> {
            if (this.faceTrackModel.size() == FACE_TRACK_SIZE) {
                this.faceTrackModel.remove(faceTrackModel.size() - 1);
            }
            if (!this.faceTrackModel.contains(faceTrack)) {
                this.faceTrackModel.add(0, faceTrack);
                this.faceDetectThreadService.execute(() -> {
                    long ms = System.currentTimeMillis();
                    FaceEngineService faceEngineService = null;
                    try {
                        faceEngineService = this.faceDetectPool.borrowObject();
                        for (FaceUser faceUser : FaceUserCache.getInstance().getCaches()) {
                            float faceScore = faceEngineService.faceCompare(faceTrack.getData(), faceUser.getData());
                            DebugUtils.debug("%d\t%dms\t%.2f", faceEngineService.hashCode(), (System.currentTimeMillis() - ms), faceScore);
                            if (faceScore >= this.faceConfig.getFaceScore()) {
                                FaceTrackUser faceTrackUser = new FaceTrackUser();
                                faceTrackUser.setFaceTrack(faceTrack);
                                faceTrackUser.setFaceUser(new FaceUser(faceUser.getName(), FaceUserCache.getInstance().getCache(faceUser).getImage(), faceUser.getData()));
                                faceTrackUser.setFaceScore(faceScore);
                                this.addFaceTrackUser(faceTrackUser);
                                break;
                            }
                        }
                        DebugUtils.debug("%d\t%dms", faceEngineService.hashCode(), (System.currentTimeMillis() - ms));
                    } catch (Exception e) {
                        //
                    } finally {
                        if (faceEngineService != null) {
                            this.faceDetectPool.returnObject(faceEngineService);
                        }
                    }
                });
            }
        });
    }

    public void addFaceTrackUser(FaceTrackUser faceTrackUser) {
        SwingUtilities.invokeLater(() -> {
            if (this.faceTrackUserModel.size() == FACE_TRACK_USER_SIZE) {
                this.faceTrackUserModel.remove(this.faceTrackUserModel.size() - 1);
            }
            this.faceTrackUserModel.add(0, faceTrackUser);
        });
    }

    static class FaceTrackCellRenderer extends JLabel implements ListCellRenderer<FaceTrack> {
        private static final Dimension FACE_SIZE = new Dimension(100, 100);

        public FaceTrackCellRenderer() {
            super.setOpaque(true);
            super.setVerticalAlignment(SwingConstants.CENTER);
            super.setVerticalTextPosition(SwingConstants.BOTTOM);
            super.setHorizontalAlignment(SwingConstants.CENTER);
            super.setHorizontalTextPosition(SwingConstants.CENTER);
            super.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
        }

        @Override
        public Component getListCellRendererComponent(JList<? extends FaceTrack> list, FaceTrack value, int index, boolean selected, boolean focus) {
            BufferedImage image = ImageUtils.readImage(value.getImage());
            super.setIcon(new ImageIcon(ImageUtils.scaledImage(image, FACE_SIZE, true)));
            super.setFont(list.getFont());
            super.setToolTipText(String.format("%d-(%dx%d)", value.getId(), image.getWidth(), image.getHeight()));
            super.setBackground(selected ? list.getSelectionBackground() : list.getBackground());
            super.setForeground(selected ? list.getSelectionForeground() : list.getForeground());
            return this;
        }
    }

    static class FaceTrackUserCellRenderer extends JPanel implements ListCellRenderer<FaceTrackUser> {
        private static final Dimension FACE_SIZE = new Dimension(100, 100);
        private final JLabel faceTrack, faceScore, faceUser;

        public FaceTrackUserCellRenderer() {
            super.setOpaque(true);
            super.setLayout(new BorderLayout());
            super.add(this.faceTrack = new JLabel("", JLabel.CENTER), BorderLayout.WEST);
            super.add(this.faceScore = new JLabel("", JLabel.CENTER), BorderLayout.CENTER);
            super.add(this.faceUser = new JLabel("", JLabel.CENTER), BorderLayout.EAST);
            super.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
        }

        @Override
        public Component getListCellRendererComponent(JList<? extends FaceTrackUser> list, FaceTrackUser value, int index, boolean selected, boolean focus) {
            BufferedImage image1 = ImageUtils.readImage(value.getFaceTrack().getImage());
            BufferedImage image2 = ImageUtils.readImage(value.getFaceUser().getImage());
            this.faceTrack.setIcon(new ImageIcon(ImageUtils.scaledImage(image1, FACE_SIZE, true)));
            this.faceUser.setIcon(new ImageIcon(ImageUtils.scaledImage(image2, FACE_SIZE, true)));
            this.faceScore.setText(String.format("%.2f", value.getFaceScore()));
            super.setFont(list.getFont());
            super.setToolTipText(String.format("%d-(%dx%d) %s-(%dx%d)", value.getFaceTrack().getId(), image1.getWidth(), image1.getHeight()
                    , value.getFaceUser().getName(), image2.getWidth(), image2.getHeight()));
            super.setBackground(selected ? list.getSelectionBackground() : list.getBackground());
            super.setForeground(selected ? list.getSelectionForeground() : list.getForeground());
            return this;
        }
    }

    public static void main(String[] args) throws Exception {
        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
            StringWriter stringWriter = new StringWriter();
            e.printStackTrace(new PrintWriter(stringWriter));
            JOptionPane.showMessageDialog(null, new JScrollPane(new JTextArea(stringWriter.toString(), 10, 0)), t.toString(), JOptionPane.ERROR_MESSAGE);
            System.exit(0);
        });
        SwingUtilities.invokeLater(FaceTrackFrame::new);
    }
}
